/*
  S.M.A.C.K - An operating system kernel
  Copyright (C) 2010,2011 Mattias Holm and Kristian Rietveld
  For licensing and a full list of authors of the kernel, see the files
  COPYING and AUTHORS.
*/

#include <vfs/vfs.h>
#include <device.h>
#include <stdio.h>
#include <string.h>
#include <panic.h>

int devfs_mount(vfs_filesys_t *fs, vfs_vnode_t *mountpoint, dev_t *dev, dev_minor_t minor);
int devfs_umount(vfs_vnode_t *mountpoint);
vfs_vnode_t* devfs_root(vfs_vnode_t *mountpoint);

size_t devfs_read(vfs_file_t *fd, void *buff, size_t len);
size_t devfs_write(vfs_file_t *fd, const void *buff, size_t len);
int devfs_seek(vfs_file_t *fd, off_t offset, int whence);
int devfs_open(vfs_vnode_t *vnode);
int devfs_close(vfs_file_t *fd);
int devfs_create(vfs_vnode_t *parent);
int devfs_ioctl(vfs_file_t *fd, uint32_t request, uint32_t param);
int devfs_mmap(vfs_vnode_t *vnode);
int devfs_fsync(vfs_vnode_t *vnode);
int devfs_mkdir(vfs_vnode_t *parent, const char *dirname);
int devfs_readdir(vfs_vnode_t *vnode, size_t *dirlen, vfs_vnode_t ***dircache);
int devfs_rmdir(vfs_vnode_t *dir);

static vfs_vnode_t *devfsroot;

vfs_filesys_t devfs = {
  .fsname = "devfs",
  .mount = devfs_mount,
  .umount = devfs_umount,
  .root = devfs_root,
};


static vfs_fileops_t devfs_fileops = {
  .create = devfs_create,
  .read = devfs_read,
  .write = devfs_write,
  .seek = devfs_seek,
  .open = devfs_open,
  .close = devfs_close,
  .ioctl = devfs_ioctl,
  .fsync = devfs_fsync,
  .mmap = devfs_mmap,
  .mkdir = devfs_mkdir,
  .rmdir = devfs_rmdir,
  .readdir = devfs_readdir,
};



int
devfs_mount(vfs_filesys_t *fs, vfs_vnode_t *mountpoint, dev_t *dev, dev_minor_t minor)
{
  char devname[64];

  devfsroot = vfs_mknode();
  devfsroot->fs = fs;
  memset(devfsroot->name, 0, sizeof(devfsroot->name));
  devfsroot->parent = mountpoint;
  devfsroot->type = VFS_DIR;
  devfsroot->fops = &devfs_fileops;

  dev_t *device = NULL;
  size_t devcount = 0;

  for (size_t i = 0 ; i < MAX_DEVICES ; i ++) {
    device = dev_find(i);
    if (device != NULL) {
      devcount += device->minor_count;
    }
  }
  device = NULL;
  devfsroot->special.dir.files_in_dir = devcount;
  devfsroot->special.dir.files = vfs_getdirp(devcount);
  int devno = 0;
  for (size_t i = 0 ; i < MAX_DEVICES ; i ++) {
    device = dev_find(i);
    if (device != NULL) {
      devfsroot->special.dir.files[devno] = vfs_mkdev(devfsroot, device->mountname, device, 0);
      devno ++;
      for (dev_minor_t dminor = 1 ; dminor < device->minor_count ; dminor ++) {
        snprintf(devname, sizeof(devname), "%s%d", device->mountname, (int)dminor-1);
        devfsroot->special.dir.files[devno] = vfs_mkdev(devfsroot, devname, device, dminor);
        devno ++;
      }
    }
  }

  return 0; // Always succeed for now
}

int
devfs_umount(vfs_vnode_t *mountpoint)
{
  if (mountpoint->type != VFS_MOUNT) {
    panic();
  }

  return 0;
}


vfs_vnode_t*
devfs_root(vfs_vnode_t *mountpoint)
{
  return devfsroot;
}




size_t
devfs_read(vfs_file_t *fd, void *buff, size_t len)
{
  if (fd->vnode->type == VFS_DEVICE) {
    dev_t *dev = fd->vnode->special.dev.dev;
    return dev->ops->read(dev, fd->vnode->special.dev.minor, buff, len);
  }
  return 0;
}


size_t
devfs_write(vfs_file_t *fd, const void *buff, size_t len)
{
  if (fd->vnode->type == VFS_DEVICE) {
    dev_t *dev = fd->vnode->special.dev.dev;
    return dev->ops->write(dev, fd->vnode->special.dev.minor, buff, len);
  }
  return 0;
}


int
devfs_seek(vfs_file_t *fd, off_t offset, int whence)
{
  if (fd->vnode->type == VFS_DEVICE) {
    dev_t *dev = fd->vnode->special.dev.dev;
    return dev->ops->seek(dev, fd->vnode->special.dev.minor, offset, whence);
  }
  return -1;
}

int
devfs_open(vfs_vnode_t *vnode)
{
  if (vnode->type == VFS_DEVICE) {
    dev_t *dev = vnode->special.dev.dev;
    return dev->ops->open(dev, vnode->special.dev.minor);
  }

  return -1;
}

int
devfs_close(vfs_file_t *fd)
{
  if (fd->vnode->type == VFS_DEVICE) {
    dev_t *dev = fd->vnode->special.dev.dev;
    return dev->ops->close(dev, fd->vnode->special.dev.minor);
  }
  return -1;
}

int
devfs_create(vfs_vnode_t *parent)
{
  return -1;
}


int
devfs_ioctl(vfs_file_t *fd, uint32_t request, uint32_t param)
{
  if (fd->vnode->type == VFS_DEVICE) {
    dev_t *dev = fd->vnode->special.dev.dev;
    return dev->ops->ioctl(dev, fd->vnode->special.dev.minor, request, param);
  }
  return -1;
}


int
devfs_mmap(vfs_vnode_t *vnode)
{
  return -1;
}


int
devfs_fsync(vfs_vnode_t *vnode)
{
  return -1;
}


int
devfs_mkdir(vfs_vnode_t *parent, const char *dirname)
{
  return -1;
}


int
devfs_readdir(vfs_vnode_t *vnode, size_t *dirlen, vfs_vnode_t ***dircache)
{
  if (vnode->type == VFS_DIR) {
    return -1; // For now, this is handled by the mount function
  }

  return -1;
}


int
devfs_rmdir(vfs_vnode_t *dir)
{
  return -1;
}





void
devfs_init(void)
{
  vfs_register(&devfs);
}
